﻿using System;
using System.Collections.Generic;
using System.Text;
using System.DirectoryServices;
using System.DirectoryServices.AccountManagement;

namespace ICeFramework.Common.ActiveDirectory
{
    public class ActiveDirectoryManager : IDisposable
    {
        private const string UserSearchFilter = "(objectCategory=user)(objectClass=person)";
        private const int AccountDisableBitFlags = 2; //see http://support.microsoft.com/kb/305144
        //Not disabled accounts condition: !(useraccountcontrol:1.2.840.113556.1.4.803:=2)

        private string _AD_LocationCodeName = string.Empty;
        #region public string AD_LocationCodeName
        /// <summary>
        /// Get/Sets the AD_LocationCodeName of the ActiveDirectoryManager
        /// </summary>
        /// <value></value>
        public string AD_LocationCodeName
        {
            get { return _AD_LocationCodeName; }
            set
            {
                if (!string.IsNullOrEmpty(value))
                    _AD_LocationCodeName = value;
            }
        }
        #endregion

        private string _AD_cn = "cn";
        #region public string AD_cn
        /// <summary>
        /// Get/Sets the AD_cn of the ActiveDirectoryManager
        /// </summary>
        /// <value></value>
        public string AD_cn
        {
            get { return _AD_cn; }
            set
            {
                if (!string.IsNullOrEmpty(value))
                    _AD_cn = value;
            }
        }
        #endregion

        private string _AD_sAMAccountName = "sAMAccountName";
        #region public string AD_sAMAccountName
        /// <summary>
        /// Get/Sets the AD_sAMAccountName of the ActiveDirectoryManager
        /// </summary>
        /// <value></value>
        public string AD_sAMAccountName
        {
            get { return _AD_sAMAccountName; }
            set
            {
                if (!string.IsNullOrEmpty(value))
                    _AD_sAMAccountName = value;
            }
        }
        #endregion

        private string _AD_userAccountControl = "userAccountControl";
        #region public string AD_userAccountControl
        /// <summary>
        /// Get/Sets the AD_userAccountControl of the ActiveDirectoryManager
        /// </summary>
        /// <value></value>
        public string AD_userAccountControl
        {
            get { return _AD_userAccountControl; }
            set
            {
                if (!string.IsNullOrEmpty(value))
                    _AD_userAccountControl = value;
            }
        }
        #endregion

        private string _AD_givenName = "givenName";
        #region public string AD_givenName
        /// <summary>
        /// Get/Sets the AD_givenName of the ActiveDirectoryManager
        /// </summary>
        /// <value></value>
        public string AD_givenName
        {
            get { return _AD_givenName; }
            set
            {
                if (!string.IsNullOrEmpty(value))
                    _AD_givenName = value;
            }
        }
        #endregion

        private string _AD_sn = "sn";
        #region public string AD_sn
        /// <summary>
        /// Get/Sets the AD_sn of the ActiveDirectoryManager
        /// </summary>
        /// <value></value>
        public string AD_sn
        {
            get { return _AD_sn; }
            set
            {
                if (!string.IsNullOrEmpty(value))
                    _AD_sn = value;
            }
        }
        #endregion

        private string _AD_distinguishedName = "distinguishedName";
        #region public string AD_distinguishedName
        /// <summary>
        /// Get/Sets the AD_distinguishedName of the ActiveDirectoryManager
        /// </summary>
        /// <value></value>
        public string AD_distinguishedName
        {
            get { return _AD_distinguishedName; }
            set
            {
                if (!string.IsNullOrEmpty(value))
                    _AD_distinguishedName = value;
            }
        }
        #endregion

        private string _AD_userPrincipalName = "userPrincipalName";
        #region public string AD_userPrincipalName
        /// <summary>
        /// Get/Sets the AD_userPrincipalName of the ActiveDirectoryManager
        /// </summary>
        /// <value></value>
        public string AD_userPrincipalName
        {
            get { return _AD_userPrincipalName; }
            set
            {
                if (!string.IsNullOrEmpty(value))
                    _AD_userPrincipalName = value;
            }
        }
        #endregion

        private string _AD_mail = "mail";
        #region public string AD_mail
        /// <summary>
        /// Get/Sets the AD_mail of the ActiveDirectoryManager
        /// </summary>
        /// <value></value>
        public string AD_mail
        {
            get { return _AD_mail; }
            set
            {
                if (!string.IsNullOrEmpty(value))
                    _AD_mail = value;
            }
        }
        #endregion

        private string _rootPath;
        #region protected string RootPath
        /// <summary>
        /// Get/Sets the RootPath of the ActiveDirectoryManager
        /// </summary>
        /// <value></value>
        protected string RootPath
        {
            get { return _rootPath; }
            set { _rootPath = value; }
        }
        #endregion

        #region public ActiveDirectoryManager()
        /// <summary>
        /// Initializes a new instance of the <b>ActiveDirectoryManager</b> class.
        /// </summary>
        public ActiveDirectoryManager()
            : this(null)
        {

        }
        #endregion

        #region public void Dispose()
        /// <summary>
        /// Releases the resources used by the <b>ActiveDirectoryManager</b>.
        /// </summary>
        public void Dispose()
        {
            GC.SuppressFinalize(this);
            _rootPath = null;
            _AD_LocationCodeName = null;
            _AD_cn = null;
            _AD_distinguishedName = null;
            _AD_givenName = null;
            _AD_mail = null;
            _AD_sAMAccountName = null;
            _AD_sn = null;
            _AD_userAccountControl = null;
            _AD_userPrincipalName = null;
        }
        #endregion

        #region public ActiveDirectoryManager(string rootPath)
        /// <summary>
        /// Initializes a new instance of the <b>ActiveDirectoryManager</b> class.
        /// </summary>
        /// <param name="rootPath"></param>
        public ActiveDirectoryManager(string rootPath)
        {
            if (string.IsNullOrEmpty(rootPath))
            {
                this.RootPath = this.GetDefaultRootPath();
            }
            else
            {
                this.RootPath = rootPath;
            }
        }
        #endregion

        #region public List<string> GetAvailableDomainNames()
        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public List<string> GetAvailableDomainNames()
        {
            List<string> domainNames = new List<string>();
            DirectorySearcher srch = new DirectorySearcher("objectCategory=Domain");
            SearchResultCollection domainCollection = srch.FindAll();
            foreach (SearchResult result in domainCollection)
            {
                domainNames.Add(result.Properties["name"][0].ToString());
            }
            return domainNames;
        }
        #endregion

        #region public UserInfo GetUserInfo(string userAccountName)
        /// <summary>
        /// 
        /// </summary>
        /// <param name="userAccountName"></param>
        /// <returns></returns>
        public UserInfo GetUserInfo(string userAccountName)
        {
            return this.GetUserInfo(null, userAccountName);
        }
        #endregion

        #region public UserInfo GetUserInfo(string domainName, string userAccountName)
        /// <summary>
        /// Returns null if the account was not found or it is disabled
        /// Throws ApplicationException if there are more than one accounts with this name
        /// </summary>
        /// <param name="domainName"></param>
        /// <param name="userAccountName"></param>
        /// <returns></returns>
        public UserInfo GetUserInfo(string domainName, string userAccountName)
        {
            if (string.IsNullOrEmpty(userAccountName))
            {
                throw new ArgumentException("userAccountName");
            }
            if (string.IsNullOrEmpty(this.RootPath))
            {
                throw new ActiveDirectoryException("RootPath not set");
            }
            try
            {
                DirectoryEntry entry = this.GetRootDirectoryEntry(domainName);
                using (entry)
                {
                    using (DirectorySearcher searcher = new DirectorySearcher(entry))
                    {
                        List<string> _propNamesList = new List<string>();
                        if (!_propNamesList.Contains(this.AD_cn))
                        {
                            _propNamesList.Add(this.AD_cn);
                        }
                        if (!_propNamesList.Contains(this.AD_distinguishedName))
                        {
                            _propNamesList.Add(this.AD_distinguishedName);
                        }
                        if (!_propNamesList.Contains(this.AD_givenName))
                        {
                            _propNamesList.Add(this.AD_givenName);
                        }
                        if (!_propNamesList.Contains(this.AD_LocationCodeName))
                        {
                            _propNamesList.Add(this.AD_LocationCodeName);
                        }
                        if (!_propNamesList.Contains(this.AD_mail))
                        {
                            _propNamesList.Add(this.AD_mail);
                        }
                        if (!_propNamesList.Contains(this.AD_sAMAccountName))
                        {
                            _propNamesList.Add(this.AD_sAMAccountName);
                        }
                        if (!_propNamesList.Contains(this.AD_sn))
                        {
                            _propNamesList.Add(this.AD_sn);
                        }
                        if (!_propNamesList.Contains(this.AD_userAccountControl))
                        {
                            _propNamesList.Add(this.AD_userAccountControl);
                        }
                        if (!_propNamesList.Contains(this.AD_userPrincipalName))
                        {
                            _propNamesList.Add(this.AD_userPrincipalName);
                        }
                        searcher.PropertiesToLoad.AddRange(_propNamesList.ToArray());
                        searcher.ReferralChasing = ReferralChasingOption.All;
                        searcher.SearchScope = SearchScope.Subtree;
                        searcher.Filter = this.CreateFilter(userAccountName, null, null);
                        SearchResultCollection results = searcher.FindAll();
                        using (results)
                        {
                            if (results.Count == 0)
                            {
                                //Account not found
                                return null;
                            }
                            if (results.Count > 1)
                            {
                                throw new ActiveDirectoryException(string.Format("There are {0} accounts for user {1}", results.Count, userAccountName));
                            }
                            UserInfo user = new UserInfo();
                            this.UpdateUserInfo(domainName, user, results[0]);
                            return user;
                        }

                    }
                }
            }
            catch (Exception ex)
            {
                throw new ActiveDirectoryException(string.Format("Error loading user {0}", userAccountName), ex);
            }
        }
        #endregion

        #region protected DirectoryEntry GetRootDirectoryEntry(string domainName)
        /// <summary>
        /// 
        /// </summary>
        /// <param name="domainName"></param>
        /// <returns></returns>
        protected DirectoryEntry GetRootDirectoryEntry(string domainName)
        {
            DirectoryEntry entry;
            if (string.IsNullOrEmpty(domainName))
            {
                entry = new DirectoryEntry(this.RootPath);
            }
            else
            {
                entry = this.GetDomainEntry(domainName);
                if (entry == null)
                {
                    throw new ActiveDirectoryException(string.Format("Domain not found: {0}", domainName));
                }
            }
            return entry;
        }
        #endregion

        #region public IList<UserInfo> FindUsers(string accountMask, string firstNameMask, string lastNameMask)
        /// <summary>
        /// 
        /// </summary>
        /// <param name="accountMask"></param>
        /// <param name="firstNameMask"></param>
        /// <param name="lastNameMask"></param>
        /// <returns></returns>
        public IList<UserInfo> FindUsers(string accountMask, string firstNameMask, string lastNameMask)
        {
            return this.FindUsers(null, accountMask, firstNameMask, lastNameMask);
        }
        #endregion

        //private static DirectoryEntry _entry;
        //public static void InitRootDirectoryEntry(string dm)
        //{
        //    if (_entry == null)
        //    {
        //        _entry = this.GetRootDirectoryEntry(domainName);
        //    }
        //} 

        //public InitRootDirectoryEntryParams(
        //{

        //}

        #region public IList<UserInfo> FindUsers(string domainName, string accountMask, string firstNameMask, string lastNameMask)
        /// <summary>
        /// 
        /// </summary>
        /// <param name="domainName"></param>
        /// <param name="accountMask"></param>
        /// <param name="firstNameMask"></param>
        /// <param name="lastNameMask"></param>
        /// <returns></returns>
        public IList<UserInfo> FindUsers(string domainName, string accountMask, string firstNameMask, string lastNameMask)
        {
            if (string.IsNullOrEmpty(accountMask) &&
                string.IsNullOrEmpty(firstNameMask) &&
                string.IsNullOrEmpty(lastNameMask))
            {
                throw new ApplicationException("At least one parameter must be provided.");
            }
            if (string.IsNullOrEmpty(this.RootPath))
            {
                throw new ActiveDirectoryException("RootPath not set");
            }
            List<UserInfo> users = new List<UserInfo>();
            try
            {
                RootDirectoryEntry.InitDomain(domainName, this.RootPath);
                DirectoryEntry entry = RootDirectoryEntry.Instance.ADRootDirectoryEntry;

                using (DirectorySearcher searcher = new DirectorySearcher(entry))
                {
                    List<string> _propNamesList = new List<string>();
                    if (!_propNamesList.Contains(this.AD_cn))
                    {
                        _propNamesList.Add(this.AD_cn);
                    }
                    if (!_propNamesList.Contains(this.AD_distinguishedName))
                    {
                        _propNamesList.Add(this.AD_distinguishedName);
                    }
                    if (!_propNamesList.Contains(this.AD_givenName))
                    {
                        _propNamesList.Add(this.AD_givenName);
                    }
                    if (!_propNamesList.Contains(this.AD_LocationCodeName))
                    {
                        _propNamesList.Add(this.AD_LocationCodeName);
                    }
                    if (!_propNamesList.Contains(this.AD_mail))
                    {
                        _propNamesList.Add(this.AD_mail);
                    }
                    if (!_propNamesList.Contains(this.AD_sAMAccountName))
                    {
                        _propNamesList.Add(this.AD_sAMAccountName);
                    }
                    if (!_propNamesList.Contains(this.AD_sn))
                    {
                        _propNamesList.Add(this.AD_sn);
                    }
                    if (!_propNamesList.Contains(this.AD_userAccountControl))
                    {
                        _propNamesList.Add(this.AD_userAccountControl);
                    }
                    if (!_propNamesList.Contains(this.AD_userPrincipalName))
                    {
                        _propNamesList.Add(this.AD_userPrincipalName);
                    }
                    //propList.ToArray();
                    searcher.PropertiesToLoad.AddRange(_propNamesList.ToArray());
                    searcher.ReferralChasing = ReferralChasingOption.All;
                    searcher.SearchScope = SearchScope.Subtree;
                    searcher.Filter = this.CreateFilter(accountMask, firstNameMask, lastNameMask);
                    SearchResultCollection results = searcher.FindAll();
                    using (results)
                    {
                        foreach (SearchResult result in results)
                        {
                            UserInfo user = new UserInfo();
                            this.UpdateUserInfo(domainName, user, result);
                            if (!user.AccountDisabled)
                            {
                                users.Add(user);
                            }
                        }
                    }

                }
            }
            catch (Exception ex)
            {
                RootDirectoryEntry.ResetInstance();
                throw new ActiveDirectoryException(ex.Message);
            }
            return users;
        }
        #endregion

        #region protected virtual void UpdateUserInfo(string domainName, UserInfo userInfo, SearchResult result)
        /// <summary>
        /// 
        /// </summary>
        /// <param name="domainName"></param>
        /// <param name="userInfo"></param>
        /// <param name="result"></param>
        protected virtual void UpdateUserInfo(string domainName, UserInfo userInfo, SearchResult result)
        {
            userInfo.Name = (string)this.GetSinglePropValue(result, this.AD_sAMAccountName);
            userInfo.Name = string.Format(@"{0}\{1}", domainName.ToUpper(), userInfo.Name);
            userInfo.FirstName = (string)this.GetSinglePropValue(result, this.AD_givenName);
            userInfo.LastName = (string)this.GetSinglePropValue(result, this.AD_sn);
            userInfo.EmailAddress = (string)this.GetSinglePropValue(result, this.AD_mail);
            if (!string.IsNullOrEmpty(this.AD_LocationCodeName))
            {
                userInfo.LocationCode = (string)this.GetSinglePropValue(result, this.AD_LocationCodeName);
            }
            string accountControl = (string)this.GetSinglePropValue(result, this.AD_userAccountControl);
            userInfo.AccountDisabled = (int.Parse(accountControl) & AccountDisableBitFlags) != 0;
        }
        #endregion

        #region protected DirectoryEntry GetDomainEntry(string domain)
        /// <summary>
        /// 
        /// </summary>
        /// <param name="domain"></param>
        /// <returns></returns>
        protected DirectoryEntry GetDomainEntry(string domain)
        {
            DirectorySearcher srch = new DirectorySearcher("objectCategory=Domain");
            SearchResultCollection col = srch.FindAll();
            foreach (SearchResult res in srch.FindAll())
            {
                string name = (string)this.GetSinglePropValue(res, "name");
                if (domain == name)
                {
                    return res.GetDirectoryEntry();
                }
            }
            return null;
        }
        #endregion

        #region protected object GetSinglePropValue(SearchResult result, string propName)
        /// <summary>
        /// 
        /// </summary>
        /// <param name="result"></param>
        /// <param name="propName"></param>
        /// <returns></returns>
        protected object GetSinglePropValue(SearchResult result, string propName)
        {
            if (!result.Properties.Contains(propName))
            {
                return null;
            }
            ResultPropertyValueCollection vals = result.Properties[propName];
            if (vals.Count < 0)
            {
                return null;
            }
            return vals[0].ToString();
        }
        #endregion

        #region protected virtual string CreateFilter(string accountName, string firstName, string lastName)
        /// <summary>
        /// 
        /// </summary>
        /// <param name="accountName"></param>
        /// <param name="firstName"></param>
        /// <param name="lastName"></param>
        /// <returns></returns>
        protected virtual string CreateFilter(string accountName, string firstName, string lastName)
        {
            StringBuilder sb = new StringBuilder();
            sb.Append("(&");
            sb.Append(UserSearchFilter);
            if (!string.IsNullOrEmpty(accountName))
            {
                sb.AppendFormat("({1}={0})", accountName, this.AD_sAMAccountName);
            }
            if (!string.IsNullOrEmpty(firstName))
            {
                sb.AppendFormat("({1}={0})", firstName, this.AD_givenName);
            }
            if (!string.IsNullOrEmpty(lastName))
            {
                sb.AppendFormat("({1}={0})", lastName, this.AD_sn);
            }

            sb.Append(")");
            return sb.ToString();
        }
        #endregion

        #region protected string GetDefaultRootPath()
        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        protected string GetDefaultRootPath()
        {
            string defaultNamingContext;
            DirectoryEntry adRootDSE;
            try
            {
                adRootDSE = new DirectoryEntry("LDAP://rootDSE");
            }
            catch (Exception ex)
            {
                throw new ActiveDirectoryException("Error loading root entry", ex);
            }
            using (adRootDSE)
            {
                if (adRootDSE.Properties["defaultNamingContext"] == null)
                {
                    throw new ActiveDirectoryException("Error loading root entry: defaultNamingContext property not found");
                }
                defaultNamingContext = (string)adRootDSE.Properties["defaultNamingContext"].Value;
                if (defaultNamingContext == null)
                {
                    throw new ActiveDirectoryException("Error loading root entry: defaultNamingContext is null");
                }
            }
            return string.Format("LDAP://{0}", defaultNamingContext);
        }
        #endregion

        #region public static bool IsUserInGroup(string userName, string groupName, ADContextType userContextType, ADContextType groupContextType)
        /// <summary>
        /// 
        /// </summary>
        /// <param name="userName"></param>
        /// <param name="groupName"></param>
        /// <param name="userContextType"></param>
        /// <param name="groupContextType"></param>
        /// <returns>True if user in is group, otherwise false.</returns>
        public static bool IsUserInGroup(string userName, string groupName, ADContextType userContextType, ADContextType groupContextType)
        {
            ContextType userType = (ContextType)Enum.Parse(typeof(ContextType), userContextType.ToString(), true);
            ContextType groupType = (ContextType)Enum.Parse(typeof(ContextType), groupContextType.ToString(), true);

            PrincipalContext userContext = new PrincipalContext(userType);
            PrincipalContext groupContext = new PrincipalContext(groupType);

            UserPrincipal user = UserPrincipal.FindByIdentity(
                userContext,
                IdentityType.SamAccountName,
                userName);

            if (user == null)
            {
                return false;
            }

            GroupPrincipal group = GroupPrincipal.FindByIdentity(
                groupContext, groupName);

            if (group == null)
            {
                return false;
            }

            return user.IsMemberOf(group);
        }
        #endregion

        //public static bool IsEmailInGroup(string email, string groupName, ADContextType userContextType, ADContextType groupContextType)
        //{
        //    string boEmail = string.IsNullOrEmpty(email) ? null : email.Trim().ToLower();
        //    GroupInfo gInfo = ActiveDirectoryManager.GetGroup(groupName, groupContextType);
        //    if (gInfo != null)
        //    {
        //        IList<ADObject> objList = gInfo.GetMembers(userContextType);
        //        foreach (ADObject obj in objList)
        //        {
        //            if (obj is UserInfo)
        //            {
        //                string uiEmail = string.IsNullOrEmpty((obj as UserInfo).EmailAddress) ? null : (obj as UserInfo).EmailAddress.Trim().ToLower();
        //                if (boEmail == uiEmail)
        //                {
        //                    return true;
        //                }
        //            }
        //        }
        //    }
        //    return false;
        //}

        #region public static UserInfo GetUser(string userName, ADContextType userContextType)
        /// <summary>
        /// 
        /// </summary>
        /// <param name="userName"></param>
        /// <param name="userContextType"></param>
        /// <returns></returns>
        public static UserInfo GetUser(string userName, ADContextType userContextType)
        {
            ContextType userType = (ContextType)Enum.Parse(typeof(ContextType), userContextType.ToString(), true);
            PrincipalContext userContext = new PrincipalContext(userType);

            UserPrincipal user = UserPrincipal.FindByIdentity(
                userContext,
                IdentityType.SamAccountName,
                userName);

            return UserInfo.GetUserInfo(user, userContextType);
        }
        #endregion

        #region public static GroupInfo GetGroup(string groupName, ADContextType groupContextType)
        /// <summary>
        /// 
        /// </summary>
        /// <param name="groupName"></param>
        /// <param name="groupContextType"></param>
        /// <returns></returns>
        public static GroupInfo GetGroup(string groupName, ADContextType groupContextType)
        {
            ContextType groupType = (ContextType)Enum.Parse(typeof(ContextType), groupContextType.ToString(), true);

            PrincipalContext groupContext = new PrincipalContext(groupType);

            GroupPrincipal group = GroupPrincipal.FindByIdentity(
                groupContext, groupName);

            if (group == null)
            {
                return null;
            }

            return GroupInfo.GetGroupInfo(group, groupContextType);
        }
        #endregion
    }
}
